#include "ffmpegwindow.h"
#include "ui_ffmpegwindow.h"
#include "deviceinformation.h"
#include <iostream>
#include <vector>
#include <QDebug>
#include "strmif.h"
#include <initguid.h>
#include <vector>
#include <string>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavdevice/avdevice.h>
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include <libavformat/avio.h>
#include <libavutil/file.h>
#include <SDL/SDL.h>
}
#include <QTimer>
#include <QTime>
#include <QWidget>
#include <QFile>
#include <QMessageBox>
#include <QFileDialog>
int thread_exit = 0;
int thread_pause = 0;
const char *video_device_name = "video=Lenovo EasyCamera";
using namespace std;

// Refresh Event
#define SFM_REFRESH_EVENT  (SDL_USEREVENT + 1)

#define SFM_BREAK_EVENT  (SDL_USEREVENT + 2)

FFmpegWindow::FFmpegWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::FFmpegWindow)
{
    // sdl窗口
    setWindowTitle("FFmpeg qt集成");
    mainWidget = new QWidget(this);
    setCentralWidget(mainWidget);
    initFFmpegWindow();
    show_dshow_device();
    ui->setupUi(this);
}

FFmpegWindow::~FFmpegWindow()
{
    SDL_DestroyRenderer(sdlRenderer); // Basic SDL garbage collection
    SDL_DestroyWindow(sdlWindow);

    delete Time;
    sdlRenderer = nullptr;
    sdlWindow = nullptr;
    Time = nullptr;
    delete ui;
}

void FFmpegWindow::testav() {
    pavFormatContext = avformat_alloc_context();
    pavInputFormat = av_find_input_format("dshow");
    cout << pavInputFormat << endl;
}

void FFmpegWindow::openffmpegcam() {}

void FFmpegWindow::initFFmpegWindow() {
    avformat_network_init();
    avdevice_register_all();
    pavFormatContext = avformat_alloc_context();
    pavInputFormat = av_find_input_format("dshow");
    cout << "before avdictionay" << endl;
    cout << pavInputFormat << endl;
}

void FFmpegWindow::addSDL() {
    AVFormatContext *pFormatCtx;
    int i, videoindex;
    AVCodecContext *pCodecCtx;
    AVCodec *pCodec;
    AVFrame *pFrame, *pFrameYUV;
    unsigned char *out_buffer;
    AVPacket *packet;
    int y_size;
    int ret, got_picture;
    struct SwsContext *img_convert_ctx;

    const char *filepath = "1.mp4";

    // SDL---------------------------
    int screen_w = 0, screen_h = 0;
    SDL_Window   *screen;
    SDL_Renderer *sdlRenderer;
    SDL_Texture  *sdlTexture;
    SDL_Rect sdlRect;

    FILE *fp_yuv;


    avformat_network_init();
    pFormatCtx = avformat_alloc_context();

    if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {
        printf("Couldn't open input stream.\n");
    }

    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        printf("Couldn't find stream information.\n");
    }
    videoindex = -1;

    for (i = 0; i < pFormatCtx->nb_streams; i++)
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoindex = i;
            break;
        }

    if (videoindex == -1) {
        printf("Didn't find a video stream.\n");
    }

    pCodecCtx = pFormatCtx->streams[videoindex]->codec;
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

    if (pCodec == NULL) {
        printf("Codec not found.\n");
    }

    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        printf("Could not open codec.\n");
    }

    pFrame = av_frame_alloc();
    pFrameYUV = av_frame_alloc();
    out_buffer =
        (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
                                                            pCodecCtx->width,
                                                            pCodecCtx->height,
                                                            1));
    av_image_fill_arrays(pFrameYUV->data,
                         pFrameYUV->linesize,
                         out_buffer,
                         AV_PIX_FMT_YUV420P,
                         pCodecCtx->width,
                         pCodecCtx->height,
                         1);

    packet = (AVPacket *)av_malloc(sizeof(AVPacket));

    // Output Info-----------------------------
    printf("--------------- File Information ----------------\n");
    av_dump_format(pFormatCtx, 0, filepath, 0);
    printf("-------------------------------------------------\n");
    img_convert_ctx = sws_getContext(pCodecCtx->width,
                                     pCodecCtx->height,
                                     pCodecCtx->pix_fmt,
                                     pCodecCtx->width,
                                     pCodecCtx->height,
                                     AV_PIX_FMT_YUV420P,
                                     SWS_BICUBIC,
                                     NULL,
                                     NULL,
                                     NULL);

#if OUTPUT_YUV420P
    fp_yuv = fopen("output.yuv", "wb+");
#endif // if OUTPUT_YUV420P

    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
        printf("Could not initialize SDL - %s\n", SDL_GetError());
    }

    screen_w = pCodecCtx->width;
    screen_h = pCodecCtx->height;


    void *pWnd = (void *)centralWidget()->winId();

    sdlWindow = SDL_CreateWindowFrom(pWnd);

    // SDL 2.0 Support for multiple windows
    //    screen = SDL_CreateWindow("Simplest ffmpeg player's Window",
    //                              SDL_WINDOWPOS_UNDEFINED,
    //                              SDL_WINDOWPOS_UNDEFINED,
    //                              screen_w,
    //                              screen_h,
    //                              SDL_WINDOW_OPENGL);

    if (!screen) {
        printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
    }

    sdlRenderer = SDL_CreateRenderer(screen, -1, 0);

    // IYUV: Y + U + V  (3 planes)
    // YV12: Y + V + U  (3 planes)
    sdlTexture = SDL_CreateTexture(sdlRenderer,
                                   SDL_PIXELFORMAT_IYUV,
                                   SDL_TEXTUREACCESS_STREAMING,
                                   pCodecCtx->width,
                                   pCodecCtx->height);

    sdlRect.x = 0;
    sdlRect.y = 0;
    sdlRect.w = screen_w;
    sdlRect.h = screen_h;

    // SDL End----------------------
    while (av_read_frame(pFormatCtx, packet) >= 0) {
        if (packet->stream_index == videoindex) {
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);

            if (ret < 0) {
                printf("Decode Error.\n");
            }

            if (got_picture) {
                sws_scale(img_convert_ctx,
                          (const unsigned char *const *)pFrame->data,
                          pFrame->linesize,
                          0,
                          pCodecCtx->height,
                          pFrameYUV->data,
                          pFrameYUV->linesize);

#if OUTPUT_YUV420P
                y_size = pCodecCtx->width * pCodecCtx->height;
                fwrite(pFrameYUV->data[0], 1, y_size,     fp_yuv); // Y
                fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); // U
                fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); // V
#endif // if OUTPUT_YUV420P

                // SDL---------------------------
#if 0
                SDL_UpdateTexture(sdlTexture,
                                  NULL,
                                  pFrameYUV->data[0],
                                  pFrameYUV->linesize[0]);
#else // if 0
                SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
                                     pFrameYUV->data[0], pFrameYUV->linesize[0],
                                     pFrameYUV->data[1], pFrameYUV->linesize[1],
                                     pFrameYUV->data[2], pFrameYUV->linesize[2]);
#endif // if 0

                SDL_RenderClear(sdlRenderer);
                SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
                SDL_RenderPresent(sdlRenderer);

                // SDL End-----------------------
                // Delay 40ms
                SDL_Delay(40);
            }
        }
        av_free_packet(packet);
    }

    // flush decoder
    // FIX: Flush Frames remained in Codec
    while (1) {
        ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);

        if (ret < 0) break;

        if (!got_picture) break;
        sws_scale(img_convert_ctx,
                  (const unsigned char *const *)pFrame->data,
                  pFrame->linesize,
                  0,
                  pCodecCtx->height,
                  pFrameYUV->data,
                  pFrameYUV->linesize);
#if OUTPUT_YUV420P
        int y_size = pCodecCtx->width * pCodecCtx->height;
        fwrite(pFrameYUV->data[0], 1, y_size,     fp_yuv); // Y
        fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); // U
        fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); // V
#endif // if OUTPUT_YUV420P

        // SDL---------------------------
        SDL_UpdateTexture(sdlTexture,
                          &sdlRect,
                          pFrameYUV->data[0],
                          pFrameYUV->linesize[0]);
        SDL_RenderClear(sdlRenderer);
        SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
        SDL_RenderPresent(sdlRenderer);

        // SDL End-----------------------
        // Delay 40ms
        SDL_Delay(40);
    }

    sws_freeContext(img_convert_ctx);

#if OUTPUT_YUV420P
    fclose(fp_yuv);
#endif // if OUTPUT_YUV420P

    SDL_Quit();

    av_frame_free(&pFrameYUV);
    av_frame_free(&pFrame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
}

void FFmpegWindow::opencam() {
    if (pavInputFormat != nullptr)
    {
        cout << "opened camera" << endl;
        ret = avformat_open_input(&pavFormatContext,
                                  video_device_name,
                                  pavInputFormat,
                                  nullptr);

        if (ret != 0)
        {
            cout << "cant open input" << endl;
            avformat_free_context(pavFormatContext);
        } else {
            cout << "opened input " << endl;
        }
    }
    else
    {
        cout << "pavInputFormat is null" << endl;
    }

    // 处理视频流
    if (avformat_find_stream_info(pavFormatContext, nullptr) < 0)
    {
        cout << "cant find stream infor" << endl;
    }
    videoindex = -1;

    for (i = 0; i < pavFormatContext->nb_streams; i++)
    {
        if (pavFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoindex = i;
            cout << "find video stream" << endl;
            break;
        }
    }

    if (videoindex == -1)
    {
        cout << "cant find video stream" << endl;
    }
    pavCodeContext = pavFormatContext->streams[videoindex]->codec;
    pavCodec = avcodec_find_decoder(pavCodeContext->codec_id);

    if (pavCodec == nullptr)
    {
        cout << "codec not found" << endl;
    }

    if (avcodec_open2(pavCodeContext, pavCodec, nullptr) < 0)
    {
        cout << "cant open codec\n";
    }

    // sdl--------------------------------------------------

    pFrame = av_frame_alloc();
    pFrameYUV = av_frame_alloc();
    avformat_network_init();

    char variable[64];
    sprintf(variable, "SDL_WINDOWID=0x%lx", this->winId());

    // 视频的宽，高
    int srcWidth = pavCodeContext->width;
    int srcHeight = pavCodeContext->height;
    struct SwsContext *img_convert_ctx;

    out_buffer =
        (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
                                                            srcWidth, srcHeight,
                                                            1));

    // 这一句有错误
    //    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,
    //                         AV_PIX_FMT_YUV420P, srcWidth, srcHeight, 1);
    //    packet = (AVPacket *)av_malloc(sizeof(AVPacket)); // 视频信息存入packet
    // Output Info-----------------------------
    printf("---------------- File Information ---------------\n");

    // av_dump_format(pavFormatContext, 0, filepath, 0);
    printf("-------------------------------------------------\n");

    //    img_convert_ctx = sws_getContext(srcWidth,
    //                                     srcHeight,
    //                                     pavCodeContext->pix_fmt,
    //                                     srcWidth,
    //                                     srcHeight,
    //                                     AV_PIX_FMT_YUV420P,
    //                                     SWS_BICUBIC,
    //                                     nullptr,
    //                                     nullptr,
    //                                     nullptr);

    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
        qDebug() << "Could not initialize SDL - %s\n" << SDL_GetError();

        /*return -1;*/
    }
    screen_w = pavCodeContext->width;
    screen_h = pavCodeContext->height;

    void *pWnd = (void *)centralWidget()->winId();

    sdlWindow = SDL_CreateWindowFrom(pWnd);

    //    sdlRenderer = SDL_CreateRenderer(sdlWindow, -1,
    //                                     SDL_RENDERER_ACCELERATED);
    //    screen = SDL_CreateWindow("Simplest ffmpeg player's Window",
    //                              SDL_WINDOWPOS_UNDEFINED,
    //                              SDL_WINDOWPOS_UNDEFINED,
    //                              screen_w,
    //                              screen_h,
    //                              SDL_WINDOW_OPENGL);

    if (!screen) {
        printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
    }
    sdlRenderer = SDL_CreateRenderer(screen, -1, 0);

    // IYUV: Y + U + V  (3 planes)
    // YV12: Y + V + U  (3 planes)
    sdlTexture = SDL_CreateTexture(sdlRenderer,
                                   SDL_PIXELFORMAT_IYUV,
                                   SDL_TEXTUREACCESS_STREAMING,
                                   srcWidth,
                                   srcHeight);

    sdlRect.x = 0;
    sdlRect.y = 0;
    sdlRect.w = screen_w;
    sdlRect.h = screen_h;

    packet = (AVPacket *)av_malloc(sizeof(AVPacket));

    //    video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);

    // ------------SDL End------------
    // Event Loop

    for (;;) {
        // Wait
        SDL_WaitEvent(&event);

        if (event.type == SFM_REFRESH_EVENT) {
            while (1) {
                if (av_read_frame(pavFormatContext, packet) < 0) thread_exit = 1;

                if (packet->stream_index == videoindex) break;
            }
            ret = avcodec_decode_video2(pavCodeContext,
                                        pFrame,
                                        &got_picture,
                                        packet);

            if (ret < 0) {
                printf("Decode Error.\n");

                // return -1;
            }

            if (got_picture) {
                sws_scale(img_convert_ctx,
                          (const unsigned char *const *)pFrame->data,
                          pFrame->linesize,
                          0,
                          srcHeight,
                          pFrameYUV->data,
                          pFrameYUV->linesize);

                // SDL---------------------------
                SDL_UpdateTexture(sdlTexture,
                                  NULL,
                                  pFrameYUV->data[0],
                                  pFrameYUV->linesize[0]);
                SDL_RenderClear(sdlRenderer);

                // SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect
                // );
                SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
                SDL_RenderPresent(sdlRenderer);

                // SDL End-----------------------
            }
            av_free_packet(packet);
        }
        else if (event.type == SDL_KEYDOWN) {
            // Pause
            if (event.key.keysym.sym == SDLK_SPACE) thread_pause = !thread_pause;
        }
        else if (event.type == SDL_QUIT) {
            thread_exit = 1;
        }
        else if (event.type == SFM_BREAK_EVENT) {
            break;
        }
    }

    sws_freeContext(img_convert_ctx);

    SDL_Quit();

    // --------------
    av_frame_free(&pFrameYUV);
    av_frame_free(&pFrame);
    avcodec_close(pavCodeContext);
    avformat_close_input(&pavFormatContext);

    cout << "end here" << endl;
}

int FFmpegWindow::sfp_refresh_thread() {
    thread_exit = 0;
    thread_pause = 0;

    while (!thread_exit) {
        if (!thread_pause) {
            SDL_Event event;
            event.type = SFM_REFRESH_EVENT;
            SDL_PushEvent(&event);
        }
        SDL_Delay(40);
    }
    thread_exit = 0;
    thread_pause = 0;

    // Break
    SDL_Event event;
    event.type = SFM_BREAK_EVENT;
    SDL_PushEvent(&event);

    return 0;
}

void FFmpegWindow::show_dshow_device() {
    AVFormatContext *pFormatCtx = avformat_alloc_context();
    AVDictionary    *options = nullptr;

    av_dict_set(&options, "list_devices", "true", 0);
    AVInputFormat *iformat = av_find_input_format("dshow");
    cout << iformat << endl;
    printf("========Device Info=============\n");
    int result =
        avformat_open_input(&pFormatCtx, "video=dummy", iformat, &options);
    cout << "open cam " << result << endl;
    printf("================================\n");
}

void FFmpegWindow::on_pushButtonShowDeviceInfo_clicked()
{
    deviceinfo.show();
}

void FFmpegWindow::on_pushButtonOpenFFmpeg_clicked()
{
    //    opencam();
    addSDL();

    //    SDLInit();
}
